Skip to main content

TMFor&MapViews[Unreleased]

UNRELEASED

The For* and Map* methods are marked `@unreleased. Their API may change before they are stabilized.

The For* and Map* methods turn a collection inside a TableManager into a reactive view: a per-item reconciler that runs setup as items appear and teardown as they leave, or a derived manager whose contents are computed from a source. This guide covers both.

The reconciler model

Each For* method calls your handler once per item and hands it a per-item Janitor. Everything you add to that Janitor is cleaned up automatically when the item leaves the collection (removed, replaced, or the subscription disconnects). You never manually track which items came and went.

All For* methods return a Connection; disconnect it (or destroy the manager) to tear down every item's Janitor at once.

ForKeys / ForValues / ForPairs

The three differ in what counts as "the same item", i.e. when the handler re-runs versus stays put:

  • ForKeys(path, handler, options?) — reconciles by KEY. The handler runs once per key and is NOT re-run when the value at an existing key changes. The Janitor is destroyed when the key is removed.
  • ForValues(path, handler, options?) — reconciles by VALUE (as a multiset). The handler runs once per value occurrence and is NOT re-run when that value moves between keys/indices (e.g. an ArraySwapRemove).
  • ForPairs(path, handler, options?) — reconciles by KEY AND VALUE. The handler re-runs (tearing down the previous Janitor first) whenever either the key or the value changes.
local manager = TableManager.new({
	Enemies = { goblin = { hp = 10 }, orc = { hp = 20 } },
})

manager:ForKeys("Enemies", function(itemJanitor, key)
	local model = spawnEnemyModel(key)
	itemJanitor:Add(model) -- destroyed automatically when `key` is removed
end)

ForOptions

Both For* and their handlers accept a ForOptions:

  • FireForExisting — defaults to true: run the handler for items already present when you subscribe. Set false to react only to future items.
  • Defer — defer the initial fire (a non-immediate ListenerFireMode defers it anyway).

The handler's last argument is the same ChangeMetadata as a listener — nil on the initial fire.

MapKeys / MapValues / MapPairs

Where For* runs side effects, Map* produces a new, live TableManager derived from the source:

  • MapKeys(path, transform) — output keyed by transform(janitor, key, value), values passed through unchanged. transform re-runs only when a source key is added/removed.
  • MapValues(path, transform) — output keyed by the source key, each entry recomputed via transform(janitor, value, key) when its value changes.
  • MapPairs(path, transform)transform(janitor, key, value) returns (outputKey, outputValue); the entry recomputes when the source key or value changes. On an output-key collision, the last write wins.
-- A view of enemy display names keyed by id:
local names = manager:MapValues("Enemies", function(_janitor, enemy, id)
	return `{id} ({enemy.hp} HP)`
end)

names:Observe("goblin", function(label)
	print(label)
end)

The returned manager owns the source subscription: destroying it disconnects from the source and tears down every entry's Janitor. Destroying the SOURCE does not cascade-destroy the derived manager — their lifecycles are independent.

Unreleased

The Map* methods are marked @unreleased. The API described here may change before they are stabilized.


See also

Show raw api
{
    "functions": [],
    "properties": [],
    "types": [],
    "name": "TM For & Map Views [Unreleased]",
    "desc": ":::note UNRELEASED\nThe `For*` and `Map*` methods are marked `@unreleased.\nTheir API may change before they are stabilized.\n:::\n\nThe `For*` and `Map*` methods turn a collection inside a\n[TableManager](/api/TableManager) into a reactive view: a per-item reconciler\nthat runs setup as items appear and teardown as they leave, or a derived\nmanager whose contents are computed from a source. This guide covers both.\n\n## The reconciler model\n\nEach `For*` method calls your handler once per item and hands it a per-item\n[Janitor](/api/Janitor). Everything you add to that Janitor is cleaned up\nautomatically when the item leaves the collection (removed, replaced, or the\nsubscription disconnects). You never manually track which items came and went.\n\nAll `For*` methods return a `Connection`; disconnect it (or destroy the\nmanager) to tear down every item's Janitor at once.\n\n## ForKeys / ForValues / ForPairs\n\nThe three differ in what counts as \"the same item\", i.e. when the handler\nre-runs versus stays put:\n\n- **`ForKeys(path, handler, options?)`** — reconciles by KEY. The handler runs\n  once per key and is NOT re-run when the value at an existing key changes. The\n  Janitor is destroyed when the key is removed.\n- **`ForValues(path, handler, options?)`** — reconciles by VALUE (as a\n  multiset). The handler runs once per value occurrence and is NOT re-run when\n  that value moves between keys/indices (e.g. an `ArraySwapRemove`).\n- **`ForPairs(path, handler, options?)`** — reconciles by KEY AND VALUE. The\n  handler re-runs (tearing down the previous Janitor first) whenever either the\n  key or the value changes.\n\n```lua\nlocal manager = TableManager.new({\n\tEnemies = { goblin = { hp = 10 }, orc = { hp = 20 } },\n})\n\nmanager:ForKeys(\"Enemies\", function(itemJanitor, key)\n\tlocal model = spawnEnemyModel(key)\n\titemJanitor:Add(model) -- destroyed automatically when `key` is removed\nend)\n```\n\n### ForOptions\n\nBoth `For*` and their handlers accept a [ForOptions](/api/TableManager#ForOptions):\n\n- `FireForExisting` — defaults to `true`: run the handler for items already\n  present when you subscribe. Set `false` to react only to future items.\n- `Defer` — defer the initial fire (a non-immediate `ListenerFireMode` defers\n  it anyway).\n\nThe handler's last argument is the same `ChangeMetadata` as a listener — `nil`\non the initial fire.\n\n## MapKeys / MapValues / MapPairs\n\nWhere `For*` runs side effects, `Map*` produces a **new, live TableManager**\nderived from the source:\n\n- **`MapKeys(path, transform)`** — output keyed by `transform(janitor, key,\n  value)`, values passed through unchanged. `transform` re-runs only when a\n  source key is added/removed.\n- **`MapValues(path, transform)`** — output keyed by the source key, each entry\n  recomputed via `transform(janitor, value, key)` when its value changes.\n- **`MapPairs(path, transform)`** — `transform(janitor, key, value)` returns\n  `(outputKey, outputValue)`; the entry recomputes when the source key or value\n  changes. On an output-key collision, the last write wins.\n\n```lua\n-- A view of enemy display names keyed by id:\nlocal names = manager:MapValues(\"Enemies\", function(_janitor, enemy, id)\n\treturn `{id} ({enemy.hp} HP)`\nend)\n\nnames:Observe(\"goblin\", function(label)\n\tprint(label)\nend)\n```\n\nThe returned manager **owns** the source subscription: destroying it\ndisconnects from the source and tears down every entry's Janitor. Destroying\nthe SOURCE does not cascade-destroy the derived manager — their lifecycles are\nindependent.\n\n:::info Unreleased\nThe `Map*` methods are marked `@unreleased`. The API described here may change\nbefore they are stabilized.\n:::\n\n---\n### See also\n\n- **[TM Listeners & Fire Modes](/api/TM%20Listeners%20&%20Fire%20Modes)** — the listeners these reconcilers are built on.\n- **[TM Getting Started](/api/TM%20Getting%20Started)** — creating and reading a manager.\n- **[TM Flushing](/api/TM%20Flushing)** — how initial fires interact with deferred scheduling.",
    "source": {
        "line": 104,
        "path": "lib/tablemanager/src/Docs/TM_For_And_Map_Reactive_Views.luau"
    }
}